/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "nweb_impl.h"

#include <unistd.h>
#include <cerrno>
#include <iostream>
#include <map>
#include <string>
#include <thread>

#include "base/lazy_instance.h"
#include "nweb_delegate_adapter.h"
#include "nweb_export.h"
#include "nweb_handler.h"
#include "nweb_hilog.h"

namespace {
uint32_t g_nweb_count = 0;
const uint32_t kSurfaceMaxWidth = 7680;
const uint32_t kSurfaceMaxHeight = 7680;

// For NWebEx
bool g_browser_service_api_enabled = false;
}

using namespace OHOS::NWeb;
extern "C" OHOS_NWEB_EXPORT NWeb* CreateNWeb(
    const NWebCreateInfo& create_info) {
  static uint32_t current_nweb_id = 0;
  uint32_t nweb_id = ++current_nweb_id;
  WVLOG_I("creating nweb %{public}u, size %{public}u*%{public}u", nweb_id,
          create_info.width, create_info.height);
  auto nweb = new NWebImpl(nweb_id);
  if (nweb == nullptr) {
    WVLOG_E("fail to create nweb instance");
    return nullptr;
  }

  if (!nweb->Init(create_info)) {
    WVLOG_E("fail to init nweb");
    return nullptr;
  }

  ++g_nweb_count;
  return nweb;
}

namespace OHOS::NWeb {

// For NWebEx
typedef std::unordered_map<int32_t, NWebImpl*> NWebMap;
base::LazyInstance<NWebMap>::DestructorAtExit g_nweb_map =
    LAZY_INSTANCE_INITIALIZER;

base::LazyInstance<std::vector<std::string>>::DestructorAtExit g_browser_args =
    LAZY_INSTANCE_INITIALIZER;

NWebImpl* NWebImpl::FromID(int32_t nweb_id) {
  NWebMap* map = g_nweb_map.Pointer();
  auto it = map->find(nweb_id);
  return it == map->end() ? nullptr : it->second;
}

void NWebImpl::InitBrowserServiceApi(std::vector<std::string>& browser_args) {
  auto args = g_browser_args.Pointer();
  args->clear();
  for (const std::string& arg : browser_args) {
    args->push_back(arg);
  }
  g_browser_service_api_enabled = true;
}

bool NWebImpl::GetBrowserServiceApiEnabled() {
  return g_browser_service_api_enabled;
}

NWebImpl::NWebImpl(uint32_t id) : nweb_id_(id) {
  g_nweb_map.Get().emplace(id, this);
}

NWebImpl::~NWebImpl() {
  g_nweb_map.Get().erase(nweb_id_);
}

bool NWebImpl::Init(const NWebCreateInfo& create_info) {
  output_handler_ = NWebOutputHandler::Create(
      create_info.width, create_info.height, create_info.output_render_frame);
  if (output_handler_ == nullptr) {
    return false;
  }
  output_handler_->SetNWebId(nweb_id_);

  ProcessInitArgs(create_info.init_args);

  if (!InitWebEngine(create_info)) {
    WVLOG_E("web engine init fail");
    return false;
  }

  input_handler_ = NWebInputHandler::Create(nweb_delegate_);
  if (input_handler_ == nullptr) {
    return false;
  }

  return true;
}

void NWebImpl::OnDestroy() {
  WVLOG_I("NWebImpl::OnDestroy");
  if (g_nweb_count == 0) {
    return;
  }
  bool is_close_all = (--g_nweb_count) == 0 ? true : false;
  if (nweb_delegate_ != nullptr) {
    nweb_delegate_->OnDestroy(is_close_all);
    nweb_delegate_ = nullptr;
  }
  if (input_handler_ != nullptr) {
    input_handler_->OnDestroy();
    input_handler_ = nullptr;
  }
}

void NWebImpl::ProcessInitArgs(const NWebInitArgs& init_args) {
  if (!init_args.dump_path.empty() && output_handler_ != nullptr) {
    output_handler_->SetDumpPath(init_args.dump_path);
  }
  if (init_args.frame_info_dump && output_handler_ != nullptr) {
    output_handler_->SetFrameInfoDump(init_args.frame_info_dump);
  }

  InitWebEngineArgs(init_args);
}

bool NWebImpl::InitWebEngine(const NWebCreateInfo& create_info) {
  if (output_handler_ == nullptr) {
    WVLOG_E("fail to init web engine, NWeb output handler is not ready");
    return false;
  }
  if (web_engine_args_.empty()) {
    WVLOG_E("fail to init web engine args");
    return false;
  }

  int argc = web_engine_args_.size();
  const char** argv = new const char*[argc];
  int i = 0;
  for (auto it = web_engine_args_.begin(); i < argc; ++i, ++it) {
    argv[i] = it->c_str();
  }

  void* window =
      reinterpret_cast<void*>(output_handler_->GetNativeWindowFromSurface(
          create_info.producer_surface));
  if (window == nullptr) {
    WVLOG_E("fail to init web engine, get native window from surface failed");
    return false;
  }
  nweb_delegate_ = NWebDelegateAdapter::CreateNWebDelegate(argc, argv, window);
  if (nweb_delegate_ == nullptr) {
    WVLOG_E("fail to create nweb delegate of web engine");
    return false;
  }

  std::weak_ptr<NWebOutputHandler> output_handler_weak(output_handler_);
  auto render_update_cb = [output_handler_weak](const char* buffer) -> void {
    if (!output_handler_weak.expired()) {
      output_handler_weak.lock()->OnRenderUpdate(buffer);
    }
  };

  uint32_t width, height;
  output_handler_->GetWindowInfo(width, height);
  nweb_delegate_->Resize(width, height);
  nweb_delegate_->RegisterRenderCb(render_update_cb);

  inputmethod_handler_ = new NWebInputMethodHandler();
  nweb_delegate_->SetInputMethodClient(inputmethod_handler_);

  delete[] argv;
  return nweb_delegate_->IsReady();
}

void NWebImpl::InitWebEngineArgs(const NWebInitArgs& init_args) {
  web_engine_args_.clear();

  auto args = g_browser_args.Get();
  for (const std::string& arg : args) {
    web_engine_args_.emplace_back(arg);
  }

  web_engine_args_.emplace_back("/system/bin/web_render");
  web_engine_args_.emplace_back("--in-process-gpu");
  web_engine_args_.emplace_back("--disable-dev-shm-usage");
#ifdef GPU_RK3568
  web_engine_args_.emplace_back("--disable-gpu");
#endif
  web_engine_args_.emplace_back("--no-unsandboxed-zygote");
  web_engine_args_.emplace_back("--no-zygote");
  web_engine_args_.emplace_back("--off-screen-frame-rate=60");
  web_engine_args_.emplace_back("--enable-features=UseOzonePlatform");
  web_engine_args_.emplace_back("-ozone-platform=headless");
  web_engine_args_.emplace_back("--no-sandbox");
  web_engine_args_.emplace_back("--ignore-certificate-errors");
  web_engine_args_.emplace_back("--use-mobile-user-agent");
  web_engine_args_.emplace_back("--enable-gpu-rasterization");
  web_engine_args_.emplace_back("--enable-viewport");
  web_engine_args_.emplace_back(
      "--browser-subprocess-path=/system/bin/web_render");
  web_engine_args_.emplace_back("--zygote-cmd-prefix=/system/bin/web_render");
  web_engine_args_.emplace_back("--remote-debugging-port=9222");

  for (auto arg : init_args.web_engine_args_to_delete) {
    auto it = std::find(web_engine_args_.begin(), web_engine_args_.end(), arg);
    if (it != web_engine_args_.end()) {
      web_engine_args_.erase(it);
    }
  }
  for (auto arg : init_args.web_engine_args_to_add) {
    web_engine_args_.emplace_back(arg);
  }
  if (init_args.multi_renderer_process) {
    web_engine_args_.emplace_back("--enable-multi-renderer-process");
  }
}

void NWebImpl::PutDownloadCallback(
    std::shared_ptr<NWebDownloadCallback> downloadListener) {
  nweb_delegate_->RegisterDownLoadListener(downloadListener);
}

void NWebImpl::SetNWebHandler(std::shared_ptr<NWebHandler> client) {
  nweb_handle_ = client;
  nweb_delegate_->RegisterNWebHandler(client);
  client->SetNWeb(shared_from_this());
}

const std::shared_ptr<NWebHandler> NWebImpl::GetNWebHandler() const {
  return nweb_handle_;
}

void NWebImpl::Resize(uint32_t width, uint32_t height) {
  if (input_handler_ == nullptr || output_handler_ == nullptr) {
    return;
  }
  if (width > kSurfaceMaxWidth || height > kSurfaceMaxHeight) {
    return;
  }
  nweb_delegate_->Resize(width, height);
  output_handler_->Resize(width, height);
}

void NWebImpl::OnTouchPress(int32_t id, double x, double y) {
  if (input_handler_ == nullptr) {
    return;
  }
  input_handler_->OnTouchPress(id, x, y);
}

void NWebImpl::OnTouchRelease(int32_t id, double x, double y) {
  if (input_handler_ == nullptr) {
    return;
  }
  input_handler_->OnTouchRelease(id, x, y);
}

void NWebImpl::OnTouchMove(int32_t id, double x, double y) {
  if (input_handler_ == nullptr) {
    return;
  }
  input_handler_->OnTouchMove(id, x, y);
}

void NWebImpl::OnTouchCancel() {
  if (input_handler_ == nullptr) {
    return;
  }
  input_handler_->OnTouchCancel();
}

void NWebImpl::OnNavigateBack() {
  if (input_handler_ == nullptr) {
    return;
  }
  input_handler_->OnNavigateBack();
}

bool NWebImpl::SendKeyEvent(int32_t keyCode, int32_t keyAction) {
  if (input_handler_ == nullptr) {
    return false;
  }
  return input_handler_->SendKeyEvent(keyCode, keyAction);
}

void NWebImpl::SendMouseWheelEvent(double x, double y, double deltaX, double deltaY) {
  if (input_handler_ == nullptr) {
    return;
  }
  input_handler_->SendMouseWheelEvent(x, y, deltaX, deltaY);
}

void NWebImpl::SendMouseEvent(int x, int y, int button, int action) {
  if (input_handler_ == nullptr) {
    return;
  }
  input_handler_->SendMouseEvent(x, y, button, action);
}

void NWebImpl::Load(const std::string& url) const {
  if (nweb_delegate_ == nullptr || output_handler_ == nullptr) {
    return;
  }

  if (!output_handler_->IsSizeValid()) {
    WVLOG_E("nweb size is invalid, stop Load");
    return;
  }

  nweb_delegate_->Load(url);
  output_handler_->StartRenderOutput();
}

bool NWebImpl::IsNavigatebackwardAllowed() const {
  if (nweb_delegate_ == nullptr) {
    return false;
  }
  return nweb_delegate_->IsNavigatebackwardAllowed();
}

bool NWebImpl::IsNavigateForwardAllowed() const {
  if (nweb_delegate_ == nullptr) {
    return false;
  }
  return nweb_delegate_->IsNavigateForwardAllowed();
}

bool NWebImpl::CanNavigateBackOrForward(int numSteps) const {
  if (nweb_delegate_ == nullptr) {
    return false;
  }
  return nweb_delegate_->CanNavigateBackOrForward(numSteps);
}

void NWebImpl::NavigateBack() const {
  if (nweb_delegate_ == nullptr) {
    return;
  }
  nweb_delegate_->NavigateBack();
}

void NWebImpl::NavigateForward() const {
  if (nweb_delegate_ == nullptr) {
    return;
  }
  nweb_delegate_->NavigateForward();
}

void NWebImpl::NavigateBackOrForward(int step) const {
  if (nweb_delegate_ == nullptr) {
    return;
  }
  nweb_delegate_->NavigateBackOrForward(step);
}

void NWebImpl::DeleteNavigateHistory() {
  if (nweb_delegate_ == nullptr) {
    return;
  }
  nweb_delegate_->DeleteNavigateHistory();
}

void NWebImpl::Reload() const {
  if (nweb_delegate_ == nullptr) {
    return;
  }
  nweb_delegate_->Reload();
}

void NWebImpl::Zoom(float zoomFactor) const {
  if (nweb_delegate_ == nullptr) {
    return;
  }
  nweb_delegate_->Zoom(zoomFactor);
}

bool NWebImpl::ZoomIn() const {
  if (nweb_delegate_ == nullptr) {
    return false;
  }
  return nweb_delegate_->ZoomIn();
}

bool NWebImpl::ZoomOut() const {
  if (nweb_delegate_ == nullptr) {
    return false;
  }
  return nweb_delegate_->ZoomOut();
}

void NWebImpl::Stop() const {
  if (nweb_delegate_ == nullptr) {
    return;
  }
  nweb_delegate_->Stop();
}

void NWebImpl::ExecuteJavaScript(const std::string& code) const {
  if (nweb_delegate_ == nullptr) {
    return;
  }
  nweb_delegate_->ExecuteJavaScript(code);
}

void NWebImpl::PutBackgroundColor(int color) const {
  if (nweb_delegate_ == nullptr) {
    return;
  }
  nweb_delegate_->PutBackgroundColor(color);
}

void NWebImpl::InitialScale(float scale) const {
  if (nweb_delegate_ == nullptr) {
    return;
  }
  nweb_delegate_->InitialScale(scale);
}

void NWebImpl::OnPause() const {
  if (nweb_delegate_ == nullptr) {
    return;
  }
  nweb_delegate_->OnPause();
  if (inputmethod_handler_ == nullptr) {
    return;
  }
  inputmethod_handler_->HideTextInput();
}

void NWebImpl::OnContinue() const {
  if (nweb_delegate_ == nullptr) {
    return;
  }
  nweb_delegate_->OnContinue();
}

const std::shared_ptr<NWebPreference> NWebImpl::GetPreference() const {
  if (nweb_delegate_ == nullptr) {
    return nullptr;
  }
  return nweb_delegate_->GetPreference();
}

std::string NWebImpl::Title() {
  if (nweb_delegate_ == nullptr) {
    return "";
  }
  return nweb_delegate_->Title();
}

uint32_t NWebImpl::GetWebId() const {
  return nweb_id_;
}

HitTestResult NWebImpl::GetHitTestResult() const {
  return nweb_delegate_->GetHitTestResult();
}

int NWebImpl::PageLoadProgress() {
  if (nweb_delegate_ == nullptr) {
    return 0;
  }
  return nweb_delegate_->PageLoadProgress();
}

int NWebImpl::ContentHeight() {
  if (nweb_delegate_ == nullptr) {
    return 0;
  }
  return nweb_delegate_->ContentHeight();
}

float NWebImpl::Scale() {
  if (nweb_delegate_ == nullptr) {
    return 0;
  }
  return nweb_delegate_->Scale();
}

void NWebImpl::Load(std::string& url,
                    std::map<std::string, std::string> additionalHttpHeaders) {
  if (nweb_delegate_ == nullptr) {
    return;
  }
  return nweb_delegate_->Load(url, additionalHttpHeaders);
}

void NWebImpl::LoadWithDataAndBaseUrl(const std::string& baseUrl,
                                      const std::string& data,
                                      const std::string& mimeType,
                                      const std::string& encoding,
                                      const std::string& historyUrl) {
  if (nweb_delegate_ == nullptr) {
    return;
  }
  return nweb_delegate_->LoadWithDataAndBaseUrl(baseUrl, data, mimeType,
                                                encoding, historyUrl);
}

void NWebImpl::LoadWithData(const std::string& data,
                            const std::string& mimeType,
                            const std::string& encoding) {
  if (nweb_delegate_ == nullptr) {
    return;
  }
  return nweb_delegate_->LoadWithData(data, mimeType, encoding);
}

void NWebImpl::RegisterArkJSfunction(
    const std::string& object_name,
    const std::vector<std::string>& method_list) {
  if (nweb_delegate_ == nullptr) {
    return;
  }
  return nweb_delegate_->RegisterArkJSfunction(object_name, method_list);
}

void NWebImpl::UnregisterArkJSfunction(
    const std::string& object_name,
    const std::vector<std::string>& method_list) {
  if (nweb_delegate_ == nullptr) {
    return;
  }
  return nweb_delegate_->UnregisterArkJSfunction(object_name, method_list);
}

void NWebImpl::SetNWebJavaScriptResultCallBack(
    std::shared_ptr<NWebJavaScriptResultCallBack> callback) {
  if (nweb_delegate_ == nullptr) {
    return;
  }
  nweb_delegate_->RegisterNWebJavaScriptCallBack(callback);
}

void NWebImpl::ExecuteJavaScript(
    const std::string& code,
    std::shared_ptr<NWebValueCallback<std::string>> callback) const {
  if (nweb_delegate_ == nullptr) {
    return;
  }
  nweb_delegate_->ExecuteJavaScript(code, callback);
}

void NWebImpl::OnFocus() const {
  if (nweb_delegate_ == nullptr) {
    return;
  }
  nweb_delegate_->OnFocus();
}

void NWebImpl::OnBlur() const {
  if (nweb_delegate_ == nullptr) {
    return;
  }
  nweb_delegate_->OnBlur();
  if (inputmethod_handler_ == nullptr) {
    return;
  }
  inputmethod_handler_->HideTextInput();
}

void NWebImpl::ReloadOriginalUrl() const {
  if (nweb_delegate_ == nullptr) {
    return;
  }

  nweb_delegate_->ReloadOriginalUrl();
}

}  // namespace OHOS::NWeb
